package limitstream;

import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DigestUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author qiang.zhang
 */
@Aspect
@Component
@Order(0)
public class NotDuplicationSubmitAspect {

    private final Logger logger = LoggerFactory.getLogger(NotDuplicationSubmitAspect.class);

    private static final String DUPLICATION_PREFIX = "profile:project:duplication_prefix:";

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 环绕通知 （可以控制目标方法前中后期执行操作，目标方法执行前后分别执行一些代码）
     *
     */
    @Around("@annotation(NotDuplicationSubmit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Assert.notNull(request, "request cannot be null.");

        //获取执行方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取防重复提交注解
        NotDuplicationSubmit annotation = method.getAnnotation(NotDuplicationSubmit.class);
        String sessionId = DigestUtil.md5Hex(Optional.ofNullable(request.getHeader("Authorization")).orElse(""));

        // 获取token以及方法标记，生成redisKey和redisValue
        String redisKey = DUPLICATION_PREFIX
                .concat(sessionId)
                .concat(getMethodSign(method, joinPoint.getArgs()));

        String redisValue = redisKey.concat("_").concat(annotation.value()).concat("_").concat("submitDuplication");
        Boolean b = redisTemplate.opsForValue().setIfAbsent(redisKey, redisValue, annotation.expireMilliseconds(), TimeUnit.MILLISECONDS);
        logger.debug("防重键值：key: {}, value: {}, 设置结果：{}", redisKey, redisValue, b);
        if (Boolean.TRUE.equals(b)) {
            try {
                //正常执行方法并返回
                //ProceedingJoinPoint类型参数可以决定是否执行目标方法，且环绕通知必须要有返回值，返回值即为目标方法的返回值
                return joinPoint.proceed();
            } finally {
                //确保方法执行完毕时释放限时标记
                redisTemplate.delete(redisKey);
            }
        } else {
            throw new Exception("操作过于频繁，请勿重复提交");
        }
    }

    /**
     * 采用数字签名算法SHA1对方法签名字符串加签
     */
    private String getMethodSign(Method method, Object... args) {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args) {
            if (!Objects.isNull(arg) && arg instanceof Errors) {
                continue;
            }
            sb.append(toString(arg));
        }
        logger.info("sb===={}", sb);
        return DigestUtils.sha1DigestAsHex(sb.toString());
    }

    private String toString(Object arg) {
        if (Objects.isNull(arg)) {
            return "null";
        }
        if (arg instanceof Number) {
            return arg.toString();
        }
        return JSONObject.toJSONString(arg);
    }
}
